package com.liliudong.template.biz.service.sys.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.tree.Tree;
import cn.hutool.core.lang.tree.TreeUtil;
import com.liliudong.template.biz.mapper.sys.SysMenuMapper;
import com.liliudong.template.biz.service.sys.SysMenuService;
import com.liliudong.template.biz.service.sys.SysRoleMenuService;
import com.liliudong.template.common.constant.ExMessage;
import com.liliudong.template.common.constant.SecurityConst;
import com.liliudong.template.common.convert.sys.SysMenuConvert;
import com.liliudong.template.common.request.sys.SysMenuAddRequest;
import com.liliudong.template.common.request.sys.SysMenuModifyRequest;
import com.liliudong.template.common.request.sys.SysMenuPageRequest;
import com.liliudong.template.common.entity.BaseEntity;
import com.liliudong.template.common.entity.sys.SysMenu;
import com.liliudong.template.common.exception.BizException;
import com.liliudong.template.common.response.sys.SysMenuDetailResponse;
import com.liliudong.template.common.response.sys.SysMenuPageResponse;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static com.liliudong.template.common.entity.sys.table.SysMenuTableDef.SYS_MENU;
import static com.liliudong.template.common.entity.sys.table.SysRoleMenuTableDef.SYS_ROLE_MENU;

/**
 * 系统菜单 服务层实现。
 *
 * @author liudo
 * @since 2024-07-30
 */
@Service
@RequiredArgsConstructor
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {

    private final SysRoleMenuService sysRoleMenuService;

    @Override
    public Set<String> listPermissionByRoleIdsIgnoreDataPermission(Set<Long> roleIds, boolean isAdmin) {
        if (isAdmin) {
            return Set.of(SecurityConst.SUPER_ADMIN_ROLE_PERMISSION);
        }
        if (CollUtil.isEmpty(roleIds)) {
            return Set.of();
        }
        return queryChainIgnoreDataPermission()
                .select(SYS_MENU.PERMISSION)
                .innerJoin(SYS_ROLE_MENU)
                .on(SYS_MENU.ID.eq(SYS_ROLE_MENU.MENU_ID))
                .where(SYS_ROLE_MENU.ROLE_ID.in(roleIds))
                .list()
                .stream()
                .map(SysMenu::getPermission)
                .collect(Collectors.toSet());
    }

    @Override
    public List<Tree<Long>> treeByRoleIdsIgnoreDataPermission(Set<Long> roleIds, boolean isAdmin) {
        List<SysMenu> sysMenuList;
        if (isAdmin) {
            sysMenuList = queryChainIgnoreDataPermission().list();
        } else {
            sysMenuList = queryChainIgnoreDataPermission()
                    .innerJoin(SYS_ROLE_MENU)
                    .on(SYS_MENU.ID.eq(SYS_ROLE_MENU.MENU_ID))
                    .where(SYS_ROLE_MENU.ROLE_ID.in(roleIds))
                    .list();
        }
        return convertToTree(sysMenuList);
    }

    @Override
    public void addOne(SysMenuAddRequest sysMenuAddRequest) {
        // 参数校验
        sysMenuAddRequest.check();
        SysMenu sysMenu = SysMenuConvert.INSTANCE.addDTO2Entity(sysMenuAddRequest);
        this.save(sysMenu);
    }

    @Override
    public void deleteById(Long id) {
        // 判断菜单是否被占用
        long count = sysRoleMenuService.countByMenuId(id);
        if (count > 0) {
            throw new BizException(ExMessage.MENU_UNION_ROLE);
        }
        // 判断其子级是否有菜单
        long countChildren = countByParentId(id);
        if (countChildren > 0) {
            throw new BizException(ExMessage.MENU_UNION_MENU);
        }
        removeById(id);
    }

    @Override
    public void modifyById(Long id, SysMenuModifyRequest sysMenuModifyRequest) {
        sysMenuModifyRequest.check();
        SysMenu sysMenu = SysMenuConvert.INSTANCE.modifyDTO2Entity(sysMenuModifyRequest);
        sysMenu.setId(id);
        this.updateById(sysMenu);
    }

    @Override
    public SysMenuDetailResponse detailById(Long id) {
        return this.getOneAs(QueryWrapper.create().where(SYS_MENU.ID.eq(id)), SysMenuDetailResponse.class);
    }

    @Override
    public Page<SysMenuPageResponse> queryPage(Long page, Long size, SysMenuPageRequest sysMenuPageRequest) {
        return queryChain().pageAs(Page.of(page, size), SysMenuPageResponse.class);
    }

    @Override
    public List<Tree<Long>> queryTree() {
        List<SysMenu> sysMenuList = list();
        if (CollUtil.isEmpty(sysMenuList)) {
            return List.of();
        }
        return convertToTree(sysMenuList);
    }

    /**
     * 按父id计数
     *
     * @param menuId 菜单id
     * @return long
     */
    private long countByParentId(Long menuId) {
        return queryChain()
                .where(SYS_MENU.PARENT_ID.eq(menuId))
                .count();
    }

    /**
     * 转换为树
     *
     * @param sysMenuList sys菜单列表
     * @return {@link List }<{@link Tree }<{@link Long }>>
     */
    private static List<Tree<Long>> convertToTree(List<SysMenu> sysMenuList) {
        return TreeUtil.build(sysMenuList, 0L, (sysMenu, tree) -> {
            String component = sysMenu.getComponent();
            String icon = sysMenu.getIcon();
            String name = sysMenu.getName();
            Long parentId = sysMenu.getParentId();
            String permission = sysMenu.getPermission();
            Integer sort = sysMenu.getSort();
            SysMenu.Type type = sysMenu.getType();
            SysMenu.Visible visible = sysMenu.getVisible();
            Long id = sysMenu.getId();
            LocalDateTime createdAt = sysMenu.getCreatedAt();
            LocalDateTime updatedAt = sysMenu.getUpdatedAt();

            tree.setId(id);
            tree.setParentId(parentId);
            tree.setName(name);
            tree.setWeight(sort);
            tree.putExtra(SysMenu.Fields.component, component);
            tree.putExtra(SysMenu.Fields.permission, permission);
            tree.putExtra(SysMenu.Fields.visible, visible);
            tree.putExtra(SysMenu.Fields.type, type);
            tree.putExtra(SysMenu.Fields.icon, icon);
            tree.putExtra(BaseEntity.Fields.createdAt, createdAt);
            tree.putExtra(BaseEntity.Fields.updatedAt, updatedAt);
        });
    }

}
